#include "evm_config.h"
#include "evm_error.h"
#include "evm_memory.h"
#include "evm_opcode.h"
#include "evm_stack.h"
#include "evm_cmd_handlers.h"

#include "evm.h"

#include <stdlib.h>
#include <stdio.h>

evm* evm_new  ( void ) {
    evm*     vm = malloc( sizeof( evm ) );
    uint32_t i;

    if ( vm ) {
        vm->current   = 0;
        vm->processes = 0;

        for ( i = 0; i < 128; i += 1 ) {
            vm->cmd_handlers[ i ] = evm_nop;
        }

        vm->cmd_handlers[ PUSH    ] = evm_push;
        vm->cmd_handlers[ POP     ] = evm_pop;
        vm->cmd_handlers[ DBGDATA ] = evm_dbgdata;
        vm->cmd_handlers[ STOP    ] = evm_stop;

        vm->cmd_handlers[ ADD     ] = evm_add;
        vm->cmd_handlers[ SUB     ] = evm_sub;
        vm->cmd_handlers[ MUL     ] = evm_mul;
        vm->cmd_handlers[ DIV     ] = evm_div;
        vm->cmd_handlers[ MOD     ] = evm_mod;
    }

    return vm;
}


uint32_t evm_load ( evm *vm, const char *file_name ) {
    evm_process      *p    = evm_process_from_file( vm, file_name, file_name );
    uint32_t          res  = 0;

    if ( !vm && !file_name ) {
        return ERR_NULLPTR;
    }
    if ( !p ) {
        return ERR_PROCESS_NOT_CREATED;
    }

    res = evm_add_process( vm, p );
    if ( res != EVM_OK ) {
        evm_process_free( p );
    }

    return res;
}


uint32_t evm_load_mem ( evm *vm, const uint8_t *data, uint32_t size ) {
    static uint32_t mem_proc = 0;
    char            pname[32];
    evm_process      *p    = 0;
    uint32_t          res  = 0;

    if ( !vm && !data ) {
        return ERR_NULLPTR;
    }

    sprintf( pname, "MEMPROC_%3.3d", mem_proc ) ;
    p = evm_process_new( vm, pname );
    if ( !p ) {
        return ERR_MEMALLOC_FAILED;
    }

    // this is a working stub that not handles multiple chunks
    if ( p->memory->allocated >= size ) {
        for ( res = 0; res < size; res += 1 ) {
            p->memory->index.ptrs[0][res] = data[res];
        }
    } else {
        evm_process_free( p );
        return ERR_MEM_BOUND;
    }

    res = evm_add_process( vm, p );
    if ( res != EVM_OK ) {
        evm_process_free( p );
        return ERR_MEMALLOC_FAILED;
    }

    p->state = RUNNING;

    return EVM_OK;
}

uint32_t evm_run  ( evm *vm ) {
    uint32_t res = 0;
    if ( vm->current == 0 ) {
        return ERR_NO_PROCESS_TO_RUN;
    }

    do {
        if ( vm->current->state == RUNNING ) {
#ifdef DBG_TRACE_OPCODES
            printf("evm> executing %d opcode\n", vm->current->pc->opcode );
#endif
            res = ((cmd_handler) vm->cmd_handlers[ vm->current->pc->opcode ] ) ( vm->current );
            vm->current->pc += 1;
            if ( res != EVM_OK ) {
                // for now, just exit the VM loop
                // for future, change current process to execute
                return res;
            }
        } else {
            vm->current = 0;
        }
    } while ( vm->current );

    return EVM_OK;
}


uint32_t evm_add_process( evm *vm, struct evm_process *proc ) {
    evm_process_list *node = 0;

    if ( !vm && !proc ) {
        return ERR_NULLPTR;
    }

    if ( vm->processes == 0 ) {
        node = malloc( sizeof(evm_process_list) );
        if ( !node ) {
            return ERR_MEMALLOC_FAILED;
        }

        node->proc = proc;
        node->next = 0;

        vm->current = node->proc;
    } else {
        node = vm->processes;
        while ( node->next != 0 ) {
            node = node->next;
        }

        node->next = malloc( sizeof(evm_process_list) );
        if ( !node->next ) {
            return ERR_MEMALLOC_FAILED;
        }

        node = node->next;

        node->proc = proc;
        node->next = 0;
    }

    return EVM_OK;
}
